feat: Phase 5 — exporter, 5 tools, SSRF transport#9
Merged
Conversation
Covers 3 parts: SSRF-hardened httpx transport (PinnedDNSBackend), 5 remaining tools (web_search, wikipedia, file_read, file_write, weather), and Python code exporter. Plan reviewed through 4 passes of plan-reviewer. Also updates research-planner agent with ASCII diagram guidance and plan-review handoff loop, and marks Phase 4 as merged in READMEs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 5 of the execution layer: - SSRF transport: PinnedDNSBackend pins resolved IPs at httpcore level, preventing DNS rebinding while preserving TLS SNI. validate_url() now returns (error, resolved_ip) tuple. - 5 new tools: web_search (Tavily + DDG fallback), wikipedia (MediaWiki opensearch + wikipediaapi), file_read, file_write (sandboxed with O_NOFOLLOW + realpath), weather (Open-Meteo). - Exporter: generates standalone Python from GraphSchema with TypedDict state, node functions, routing, and requirements. - Export route upgraded from 501 stub to 200 with ExportResponse. - Registry expanded from 3 to 8 tools. 302 unit tests, 8 manual tests (30-37) all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Exporter: validate all identifiers against safe pattern before interpolation into generated Python (prevents code injection). Apply _escape() to all string values in generated code. - url_fetch: guard against empty getaddrinfo results returning (None, None) — now returns an explicit error. - file_read: fix fd leak if fstat raises unexpectedly — added finally block with ownership tracking. - file_write: wrap os.makedirs in try/except to return proper error envelope instead of uncaught 500. - web_search: wrap int(max_results) in try/except to handle non-numeric input gracefully. - exporter: _python_default uses .get() fallback for unknown types. - Added cross-owner isolation test for export route. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Success responses include {success, result, source, truncated}.
Error responses include {success, error, recoverable}.
recoverable only applies to errors — matches schema spec and
all existing tool implementations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Exporter: validate condition_branch/label values from edges against safe identifier pattern (closes remaining injection gap). - Add test_symlink_inside_sandbox_blocked_by_onofollow to verify the O_NOFOLLOW defense layer independently from the realpath pre-check (platform-aware: macOS allows read-only symlinks). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PinnedDNSBackendoverrides DNS resolution at the httpcore level, pinning resolved IPs to prevent DNS rebinding while preserving TLS SNI.validate_url()now returns(error, resolved_ip)tuple.SSRFSafeTransportsubclasseshttpx.HTTPTransportand forwardsssl_context.web_search(Tavily + DuckDuckGo fallback),wikipedia(MediaWiki opensearch API + wikipediaapi with user_agent),file_read/file_write(sandboxed withO_NOFOLLOW+realpath, UTF-8 only),weather(Open-Meteo, lat/lon detection)sandbox.pywith two-layer defense —realpathpre-check for parent dirs +O_NOFOLLOWfor leaf symlinksTypedDictstate class with correct reducer annotations, node functions, routing,if __name__block, and pip requirementsExportResponseManual tests run (30-37)
Test plan
🤖 Generated with Claude Code